package com.googlecode.horncomb.orm.ibatis;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.googlecode.horncomb.bean.filter.ConditionSet;
import com.googlecode.horncomb.bean.filter.SimpleMapConditionSet;
import com.googlecode.horncomb.bean.order.Order;
import com.googlecode.horncomb.bean.paged.CollectionWarpper;
import com.googlecode.horncomb.bean.paged.PagedList;
import com.googlecode.horncomb.orm.id.EntityIdUtil;
import com.googlecode.horncomb.util.lang.GenericsUtil;

/**
 * abstract dao for ibatis
 * @author Daniel
 */
@SuppressWarnings( { "unchecked" })
public abstract class AbstractCommonSqlMapEntityDao extends
		AbstractCommonSqlMapDao {

	/** entity class type */
	protected Class entityClass;

	/** the namespace */
	protected String namespace = null;

	/**
	 *
	 */
	public AbstractCommonSqlMapEntityDao() {
		super();
	}

	/**
	 * initialize dao: <br>
	 * 1. default get entityClass from the generic of this Class Definition <br>
	 * 2. if user statement namespace, use entityClass as namespace in default.<br>
	 * {@inheritDoc}
	 */
	@Override
	protected void initDao() throws Exception {
		super.initDao();
		if (this.entityClass == null) {
			this.entityClass = GenericsUtil.getGenericClass(this.getClass());
		}
		Assert.notNull(this.entityClass, "entityClass should not be null");
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("in [" + this.getClass() + "] entityClass is ["
					+ this.entityClass + "]");
		}

		if (this.useStatementNamespaces) {
			if (!StringUtils.hasText(this.namespace)) {
				this.namespace = this.entityClass.getName();
			}
			Assert.hasText(this.namespace, "namespace can not be empty");
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("in [" + this.getClass()
						+ "] use namespace is [" + this.namespace + "]");
			}
		}
		this.initIdLocation(false);
	}

	/**
	 * Initialize the entity class
	 * @param force
	 */
	protected void initIdLocation(boolean force) {
		try {
			EntityIdUtil.initIdLocationCache(this.entityClass, this.idName,
					force);
		} catch (IllegalStateException ex) {
			this.logger.warn(ex);
		}
	}

	protected String getIdName(Class entityType) {
		return super.getIdName(entityType == null ? this.entityClass
				: entityType);
	}

	@Override
	protected Serializable getIdValue(Object entity) {
		try {
			return EntityIdUtil.getIdValue(entity, this.getIdName(null));
		} catch (Exception ex) {
			throw new ObjectRetrievalFailureException(entity.getClass(), ex);
		}
	}

	/**
	 * create new entity object
	 * <p>
	 * {@inheritDoc}
	 */

	public Object createEntityInstance() throws Exception {
		try {
			Object entityObject = this.entityClass.newInstance();
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("create new entity: " + entityObject);
			}
			return entityObject;
		} catch (InstantiationException e) {
			throw new DataRetrievalFailureException(
					"cannot create new entity instance.", e);
		} catch (IllegalAccessException e) {
			throw new DataRetrievalFailureException(
					"cannot create new entity instance.", e);
		}
	}

	/**
	 * build statement name
	 * @param statementId
	 * @return statement name
	 */
	protected String buildStatementName(String simpleStmtName) {
		return this.buildStatementName(this.namespace, simpleStmtName);
	}

	/**
	 * delete entity by special statement
	 * @param id
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected void deleteById(Serializable id, String stmtName)
			throws DataAccessException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("delete by id: " + id);
		}
		Assert.notNull(id);
		this.doDelete(new SimpleMapConditionSet().addConstraint(this
				.getIdName(null), id), stmtName);
	}

	/**
	 * batch delete entity by special statement
	 * @param idSet
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected void deleteByIdSet(Set idSet, String stmtName)
			throws DataAccessException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("delete by id set: " + idSet);
		}
		Assert.notEmpty(idSet);
		this.doDelete(new SimpleMapConditionSet().addConstraint(this
				.getIdName(null)
				+ "s", idSet.toArray(new Object[] {})), stmtName); //$NON-NLS-1$
	}

	/**
	 * delete by constraints
	 * @param constraints
	 * @param stmtName
	 *            the name of statement
	 * @throws DataAccessException
	 */
	protected void doDelete(ConditionSet cons, String stmtName)
			throws DataAccessException {
		this.doDelete(cons, stmtName, this.namespace);
	}

	/**
	 * @param id
	 * @param stmtName
	 *            the name of statement
	 * @return
	 * @throws DataAccessException
	 */
	protected Object getById(Serializable id, String stmtName)
			throws DataAccessException {
		Assert.notNull(id);
		return this.doGet(new SimpleMapConditionSet().addConstraint(this
				.getIdName(null), id), stmtName);
	}

	/**
	 * @param cons
	 * @param stmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected Object doGet(ConditionSet cons) throws DataAccessException {
		return this.doGet(cons, this.getStatementName);
	}

	/**
	 * @param cons
	 * @param stmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected Object doGet(ConditionSet cons, String stmtName)
			throws DataAccessException {
		return this.doGet(cons, stmtName, this.namespace);
	}

	/**
	 * insert a entity to database
	 * @param entity
	 *            the entity for inserting
	 * @param stmtName
	 *            the name of statement
	 * @throws DataAccessException
	 */
	protected void insert(Object entity, String stmtName)
			throws DataAccessException {
		this.insert(entity, stmtName, this.namespace);
	}

	/**
	 * @param ids
	 * @param entity
	 * @throws DataAccessException
	 */
	protected void updateByIds(Set ids, Object entity)
			throws DataAccessException {
		this.doUpdate(new SimpleMapConditionSet().addConstraint("ids", ids),
				entity);
	}

	/**
	 * @param entity
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected void update(Object entity, String stmtName)
			throws DataAccessException {
		this.doUpdate(null, entity, stmtName);
	}

	protected void doUpdate(ConditionSet cons, Object entity)
			throws DataAccessException {
		this.doUpdate(cons, entity, this.updateStatementName);
	}

	protected void doUpdate(ConditionSet cons, Object entity, String stmtName)
			throws DataAccessException {
		this.doUpdate(cons, entity, stmtName, this.namespace);
	}

	/**
	 * @param constraints
	 * @param order
	 * @return
	 * @throws DataAccessException
	 */
	protected Collection query(ConditionSet constraints)
			throws DataAccessException {
		return this.query(constraints, null, this.queryStatementName);
	}

	/**
	 * @param constraints
	 * @param order
	 * @return
	 * @throws DataAccessException
	 */
	protected List query(ConditionSet constraints, Order order)
			throws DataAccessException {
		return this.query(constraints, order, this.queryStatementName);
	}

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @return
	 * @throws DataAccessException
	 */
	protected PagedList query(ConditionSet constraints, Order order, int page,
			int pageSize) throws DataAccessException {
		return this.query(constraints, order, page, pageSize,
				this.queryStatementName, this.queryCountStatementName);
	}

	/**
	 * 获得记录数
	 * @param constraints
	 * @return
	 */
	protected int queryCount(ConditionSet constraints) {
		return this.queryCount(constraints, this.queryCountStatementName);
	}

	// ----------------------------------------------------------------------
	// query function
	/**
	 * @param constraints
	 * @param order
	 * @param qryStmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected List query(ConditionSet constraints, Order order,
			String qryStmtName) throws DataAccessException {
		Assert.hasText(qryStmtName);
		CollectionWarpper r = this.doQuery(constraints, order, 0, 0,
				qryStmtName, null);
		return (List) r.getSource();
	}

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @param qryStmtName
	 * @param queryCountStmtId
	 * @return
	 * @throws DataAccessException
	 */
	protected PagedList query(ConditionSet constraints, Order order, int page,
			int pageSize, String qryStmtName, String queryCountStmtId)
			throws DataAccessException {
		Assert.hasText(qryStmtName);
		Assert.hasText(queryCountStmtId);
		Assert.isTrue(page > 0);
		Assert.isTrue(pageSize > 0);
		CollectionWarpper r = this.doQuery(constraints, order, page, pageSize,
				qryStmtName, queryCountStmtId);
		// always force result casted into PagedList
		return (PagedList) r;
	}

	/**
	 * @param constraints
	 * @param qryCntStmtName
	 * @return
	 */
	protected int queryCount(ConditionSet constraints, String qryCntStmtName)
			throws DataAccessException {
		return this.queryCount(constraints, qryCntStmtName, this.namespace);
	}

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @param qryStmtName
	 * @param qryCntStmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected CollectionWarpper doQuery(ConditionSet constraints, Order order,
			int page, int pageSize, String qryStmtName, String qryCntStmtName)
			throws DataAccessException {
		return this.doQuery(constraints, order, page, pageSize, qryStmtName,
				qryCntStmtName, this.namespace);
	}

	// ----------------------------------------------------------------------
	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.SqlMapEntityDao#setEntityClass(java.lang.Class)
	 */
	public void setEntityClass(Class entityClass) {
		Assert.notNull(entityClass);
		this.entityClass = entityClass;
	}

	public void setNamespace(String namespace) {
		Assert.hasText(namespace);
		this.namespace = namespace;
	}
}
